package mongo

import (
	"context"
	"gitee.com/hexug/go-tools/ioc"
	"gitee.com/hexug/go-tools/logger"
	"go.mongodb.org/mongo-driver/event"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
	"time"
)

func init() {
	ioc.Container.RegisterConf(NewMongoConfig())
}

const ConfName = "mongo"

func (m *MongoConfig) Init() error {
	return nil
}

func (m *MongoConfig) Name() string {
	return ConfName
}

// DB 对外暴露的函数
func (m *MongoConfig) DB(ctx context.Context) *mongo.Database {
	m.lock.Lock()
	defer m.lock.Unlock()
	if m.db == nil {
		m.clint(ctx)
	}
	return m.db
}

// 管理客户端
func (m *MongoConfig) clint(ctx context.Context) {
	mongoC, err := m.getMongoClint(ctx)
	if err != nil {
		panic(err)
	}
	m.db = mongoC.Database(m.Database)
}

// 生成mongodb的客户端
func (m *MongoConfig) getMongoClint(ctx context.Context) (*mongo.Client, error) {
	// 创建命令监听器
	commandMonitor := &event.CommandMonitor{
		Started: func(ctx context.Context, cmdStarted *event.CommandStartedEvent) {
			// 命令开始执行时触发
			logger.L().Debugf(
				"MONGO COMMAND STARTED: %s | %s | %v",
				cmdStarted.DatabaseName,
				cmdStarted.CommandName,
				cmdStarted.Command,
			)
		},
		Succeeded: func(ctx context.Context, cmdSucceeded *event.CommandSucceededEvent) {
			// 命令成功执行时触发
			logger.L().Debugf(
				"MONGO COMMAND SUCCEEDED: %s | %s | Duration: %vms",
				cmdSucceeded.DatabaseName,
				cmdSucceeded.CommandName,
				cmdSucceeded.Duration.Nanoseconds()/1e6,
			)
		},
		Failed: func(ctx context.Context, cmdFailed *event.CommandFailedEvent) {
			// 命令执行失败时触发
			logger.L().Errorf(
				"MONGO COMMAND FAILED: %s | %s | Error: %v",
				cmdFailed.DatabaseName,
				cmdFailed.CommandName,
				cmdFailed.Failure,
			)
		},
	}
	// 以参数的形式创建客户端
	opts := options.Client()
	// 如果设置了密码，就需要这一步
	if m.AuthSource != "" || m.Password != "" {
		cred := options.Credential{
			// 认证用户名密码的库，默认就是admin库
			AuthSource: m.AuthSource,
			// 用户名
			Username: m.Username,
			// 密码
			Password: m.Password,
			// 如果有密码，这里必须为true
			PasswordSet: true,
		}
		// 用密码就需要添加下认证
		opts.SetAuth(cred)
	}
	// 设置主机，可以设置多个主机
	opts.SetHosts(m.Endpoint).
		// 设置连接超时
		SetConnectTimeout(5 * time.Second).
		// 设置连接池
		SetMaxPoolSize(200).
		SetMonitor(commandMonitor)
	// 设置超时
	c, cancel := context.WithTimeout(ctx, time.Duration(time.Second*5))
	defer cancel()
	// 创建新客户端并连接到服务器
	client, err := mongo.Connect(c, opts)
	if err != nil {
		return nil, err
	}

	// ping测下，看是否存活
	if err := client.Ping(ctx, readpref.Primary()); err != nil {
		return nil, err
	}
	logger.L().Info("MongoDB 连接成功！")
	return client, nil
}
